use core::fmt::Write;

use crate::vm::addr::paddr::Paddr;

#[cfg(feature = "print_pl011")]
pub mod pl011;

pub trait SerialWrite {
    fn init(&mut self, addr: usize, baud: u32, clk: u32) -> Result<(), ()>;
    fn write(&mut self, buffer: &[u8]) -> Result<(), ()>;
}

struct SerialConfig {
    addr: Paddr,
    baud: u32,
    clk: u32,
}

pub struct Serial<S: SerialWrite> {
    config: SerialConfig,
    driver: S,
}

impl<S: SerialWrite> Serial<S> {
    pub fn new(driver: S) -> Self {
        Self {
            config: SerialConfig {
                addr: Paddr::from(0),
                baud: 0,
                clk: 0,
            },
            driver,
        }
    }

    #[inline]
    pub fn physaddr(&mut self, physaddr: usize) -> &mut Self {
        self.config.addr = Paddr::from(physaddr);
        self
    }

    #[inline]
    pub fn baud(&mut self, baud: u32) -> &mut Self {
        self.config.baud = baud;
        self
    }

    #[inline]
    pub fn clk(&mut self, clk: u32) -> &mut Self {
        self.config.clk = clk;
        self
    }

    #[inline]
    pub fn init(&mut self) -> Result<(), ()> {
        self.driver.init(
            self.config.addr.to_value(),
            self.config.baud,
            self.config.clk,
        )
    }
}

impl<S: SerialWrite> Write for Serial<S> {
    fn write_str(&mut self, s: &str) -> core::fmt::Result {
        self.driver
            .write(s.as_bytes())
            .map_err(|_| core::fmt::Error)
    }
}
